package com.kmxd.ams.app.arrange;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.ListUtil;
import cn.hutool.core.text.CharSequenceUtil;
import cn.hutool.core.util.BooleanUtil;
import cn.hutool.core.util.NumberUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONUtil;
import com.baomidou.mybatisplus.core.conditions.Wrapper;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.IdWorker;
import com.baomidou.mybatisplus.core.toolkit.StringUtils;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.google.common.collect.ImmutableSet;
import com.kmxd.ams.app.arrange.command.CommonQueryCmd;
import com.kmxd.ams.app.arrange.command.DataEmptyCheckCmd;
import com.kmxd.ams.app.event.AmsEventPublisher;
import com.kmxd.ams.client.archive.IArchiveFieldSvc;
import com.kmxd.ams.client.archive.IArchiveTypeSvc;
import com.kmxd.ams.client.archive.dto.ArchiveFieldQueryDTO;
import com.kmxd.ams.client.archive.vo.ArchiveFieldListVO;
import com.kmxd.ams.client.arrange.IBoxSvc;
import com.kmxd.ams.client.arrange.IDocumentSvc;
import com.kmxd.ams.client.arrange.dto.BoxSummaryDTO;
import com.kmxd.ams.client.arrange.dto.DetachDTO;
import com.kmxd.ams.client.arrange.dto.LoadBoxDTO;
import com.kmxd.ams.client.event.summary.SummaryBoxEvent;
import com.kmxd.ams.client.event.summary.SummaryProjectEvent;
import com.kmxd.ams.core.common.constant.ArchConst;
import com.kmxd.ams.core.common.constant.ArchControlConst;
import com.kmxd.ams.core.common.constant.Constants;
import com.kmxd.ams.core.common.constant.ErrorCode;
import com.kmxd.ams.core.common.enums.*;
import com.kmxd.ams.core.common.query.QueryGen;
import com.kmxd.ams.core.exception.BizTipException;
import com.kmxd.ams.core.exception.ParamsValidateException;
import com.kmxd.ams.core.util.*;
import com.kmxd.ams.infra.archive.entity.ArchiveType;
import com.kmxd.ams.infra.arrange.base.BaseBO;
import com.kmxd.ams.infra.arrange.entity.Box;
import com.kmxd.ams.infra.arrange.mapper.BoxMapper;
import java.util.*;
import java.util.concurrent.CompletableFuture;
import java.util.stream.Collectors;
import lombok.AllArgsConstructor;
import org.slf4j.MDC;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

/**
 * 盒级 服务实现类
 *
 * @author ZuoLG
 * @since 2022-07-20
 */
@Service
@AllArgsConstructor
public class BoxSvcImpl extends ServiceImpl<BoxMapper, Box> implements IBoxSvc {

  private final IDocumentSvc documentSvc;
  private final AmsEventPublisher amsEventPublisher;
  private final IArchiveFieldSvc archiveFieldSvc;
  private final CommonQueryCmd commonQueryCmd;
  private final IArchiveTypeSvc archiveTypeSvc;
  private BoxMapper boxMapper;
  private final DataEmptyCheckCmd dataEmptyCheckCmd;

  @Override
  public Page<Box> selectPage(Page reqPage, Box req) {
    if (NumberUtil.equals(req.getStatus(), Constants.TRASH_KEY)) {
      return baseMapper.selectDelByPage(reqPage, req.getTableId());
    }
    req.setStatus(NumberUtil.equals(req.getStatus(), Constants.TRASH_KEY) ? null : req.getStatus());
    QueryWrapper<Box> wrapper = QueryGen.init(new QueryWrapper<Box>(), req);
    buildSearchWrapper(req, wrapper);
    wrapper.orderBy(
        StrUtil.isNotBlank(req.getField()),
        "ascend".equals(req.getOrder()),
        StringUtils.camelToUnderline(req.getField()));

    commonQueryCmd.buildFieldOrderQuery(req, wrapper);
    // 设置数据过滤条件
    String dataConditionSql = QueryUtil.buildDataSql(req.getDataCondition());
    if (CharSequenceUtil.isNotBlank(dataConditionSql)) {
      wrapper.apply(dataConditionSql);
    }

    LambdaQueryWrapper<Box> queryWrapper = wrapper.lambda();
    if (BooleanUtil.isTrue(req.getOutArch())) {
      queryWrapper.gt(Box::getStorageNum, 0);
    }
    Set<Long> boxIds = IdUtil.split(req.getBoxIds());
    if (ObjectUtil.isNotEmpty(boxIds)) {
      queryWrapper.in(Box::getId, boxIds);
    }

    if (ObjectUtil.isEmpty(req.getControlIdentifier())) {
      List<Integer> ctls = com.google.common.collect.Lists.newArrayList(ArchControlConst.CTL_1,ArchControlConst.CTL_2,ArchControlConst.CTL_3);
      queryWrapper.in(Box::getControlIdentifier,ctls);
    }
    return this.baseMapper.selectPage(reqPage, queryWrapper);
  }

  @Override
  public List<Box> selectList(Box req) {
    req.setStatus(NumberUtil.equals(req.getStatus(), Constants.TRASH_KEY) ? null : req.getStatus());
    QueryWrapper<Box> wrapper = QueryGen.init(new QueryWrapper<Box>(), req);
    wrapper.orderBy(
        StrUtil.isNotBlank(req.getField()),
        "ascend".equals(req.getOrder()),
        StringUtils.camelToUnderline(req.getField()));

    commonQueryCmd.buildFieldOrderQuery(req, wrapper);
    // 设置数据过滤条件
    String dataConditionSql = QueryUtil.buildDataSql(req.getDataCondition());
    if (CharSequenceUtil.isNotBlank(dataConditionSql)) {
      wrapper.apply(dataConditionSql);
    }

    LambdaQueryWrapper<Box> queryWrapper = wrapper.lambda();
    if (BooleanUtil.isTrue(req.getOutArch())) {
      queryWrapper.gt(Box::getStorageNum, 0);
    }
    Set<Long> ids = IdUtil.split(req.getBoxIds());
    if (ObjectUtil.isNotEmpty(ids)) {
      queryWrapper.in(Box::getId, ids);
    }
    return boxMapper.selectList(queryWrapper);
  }

  @Override
  public List<Box> listNoDataScope(Wrapper<Box> wrapper) {
    return boxMapper.listNoDataScope(wrapper);
  }

  @Override
  public List<Box> selectListWithNoDataScope(Box req) {
    req.setStatus(NumberUtil.equals(req.getStatus(), Constants.TRASH_KEY) ? null : req.getStatus());
    QueryWrapper<Box> wrapper = QueryGen.init(new QueryWrapper<Box>(), req);
    wrapper.orderBy(
        StrUtil.isNotBlank(req.getField()),
        "ascend".equals(req.getOrder()),
        StringUtils.camelToUnderline(req.getField()));

    commonQueryCmd.buildFieldOrderQuery(req, wrapper);
    // 设置数据过滤条件
    String dataConditionSql = QueryUtil.buildDataSql(req.getDataCondition());
    if (CharSequenceUtil.isNotBlank(dataConditionSql)) {
      wrapper.apply(dataConditionSql);
    }

    LambdaQueryWrapper<Box> queryWrapper = wrapper.lambda();
    Set<Long> ids = IdUtil.split(req.getBoxIds());
    if (ObjectUtil.isNotEmpty(ids)) {
      queryWrapper.in(Box::getId, ids);
    }
    return boxMapper.selectListNoDataScope(queryWrapper);
  }

  @Override
  public List<Box> selectListByIds(Set<Long> ids) {
    LambdaQueryWrapper<Box> queryWrapper = Wrappers.lambdaQuery();
    queryWrapper.in(Box::getId, ids);
    return baseMapper.selectList(queryWrapper);
  }

  @Override
  @Transactional(rollbackFor = Exception.class)
  public void add(Box req) {
    checkData(req);
    if (ObjectUtil.isEmpty(req.getId())) {
      req.setId(IdWorker.getId());
    }
    dataEmptyCheckCmd.checkDataEmpty(req);

    // 设置默认值
    req.setDepartmentId(
        ObjectUtil.isNotEmpty(req.getDepartmentId())
            ? req.getDepartmentId()
            : SecurityUtil.getLoginUser().getDeptId());
    req.setDepartment(
        ObjectUtil.isNotEmpty(req.getDepartment())
            ? req.getDepartment()
            : SecurityUtil.getLoginUser().getDeptName());
    req.setDelFlag(DelFlagEnum.NO.getCode());
    req.setManageStatus(SecurityUtil.getManageStatus());
    boxMapper.insert(req);
    summary(req);
    MDC.put(ArchConst.UPDATE_DATA + req.getId(), JSONUtil.toJsonStr(req));
  }

  @Override
  @Transactional(rollbackFor = Exception.class)
  public void edit(Box req) {
    if (StrUtil.isNotBlank(req.getBoxIds())) {
      Set<Long> ids = IdUtil.split(req.getBoxIds());
      ids.forEach(
          item -> {
            req.setId(item);
            req.setFourCheckId(null);
            req.setFourCheckResult(null);
            editBox(req);
          });
    } else {
      req.setFourCheckId(null);
      req.setFourCheckResult(null);
      editBox(req);
    }
  }

  public void checkData(Box req) {
    if (ObjectUtil.isNull(req.getStatus())) {
      throw BizTipException.instance(ErrorCode.ARRANGE_STATUS_CAN_NOT_NULL, "档案状态不能为空");
    }
    if (!ImmutableSet.of(ArchStatusEnum.S0.getCode(), ArchStatusEnum.S1.getCode())
        .contains(req.getStatus())) {
      return;
    }
    ArchiveFieldQueryDTO qry = new ArchiveFieldQueryDTO();
    qry.setTableId(req.getTableId());
    if (ArchStatusEnum.S0.getCode().equals(req.getStatus())) {
      qry.setAttrRepeatWarn(Boolean.TRUE);
    }
    if (ArchStatusEnum.S1.getCode().equals(req.getStatus())) {
      qry.setAttrRepeatWarnArrange(Boolean.TRUE);
    }
    List<ArchiveFieldListVO> fieldList = archiveFieldSvc.selectList(qry);
    if (ObjectUtil.isEmpty(fieldList)) {
      return;
    }
    fieldList.forEach(
        item -> {
          QueryWrapper<Box> query = Wrappers.query();
          query
              .lambda()
              .ne(Objects.nonNull(req.getId()), Box::getId, req.getId())
              .eq(Box::getStatus, req.getStatus());
          query.eq(
              item.getDbFieldKey(),
              JSONUtil.parseObj(req).get(StringUtils.underlineToCamel(item.getDbFieldKey())));
          Long count = boxMapper.selectCount(query);
          long flag = null == count ? 0 : count;
          if (flag > 0) {
            throw ParamsValidateException.instance(
                StringUtils.camelToUnderline(item.getDbFieldKey()), ParamsValidateType.DATA_REPEAT);
          }
        });
  }

  /**
   * @param req
   */
  public void editBox(Box req) {
    checkData(req);
    dataEmptyCheckCmd.checkDataEmpty(req);
    Box source = boxMapper.selectById(req.getId());
    req.setUpdateBy(
        ObjectUtil.isNotEmpty(SecurityUtil.getLoginUser())
            ? SecurityUtil.getUserId()
            : req.getUpdateBy());
    req.setUpdateTime(new Date());
    boxMapper.updateById(req);
    // 在删除数据之前，保留原数据信息
    if (null == source) {
      return;
    }
    source.setArchTypeId(req.getArchTypeId());
    summary(req);
    MDC.put(ArchConst.UPDATE_DATA + req.getId(), JSONUtil.toJsonStr(req));
  }

  @Override
  @Transactional(rollbackFor = Exception.class)
  public void remove(DetachDTO req) {
    String boxTableId = req.getTableId();
    Set<Long> idList = IdUtil.split(req.getBoxIds());
    // 设置盒下的文件未未装盒
    req.setType(DetachEnum.BOX.getType());
    DynamicTableNameUtil.setTableName(req.getDocTableId());
    documentSvc.detach(req);

    DynamicTableNameUtil.setTableName(boxTableId);
    idList.forEach(
        item -> {
          Box box = boxMapper.selectById(item);
          if (ObjectUtil.isEmpty(box)) {
            // 在删除数据之前，保留原数据信息
            return;
          }
          boxMapper.deletion(req.getTableId(), ListUtil.of(item));
          box.setArchTypeId(req.getArchTypeId());
          box.setTableId(boxTableId);
        });
  }

  @Override
  public Box view(Box req) {
    return boxMapper.selectById(req.getId());
  }

  @Override
  public long count(Box req) {
    QueryWrapper<Box> query = Wrappers.query();
    QueryGen.init(query, req);
    buildSearchWrapper(req, query);
    DynamicTableNameUtil.setTableName(req.getTableId());
    Long count = boxMapper.selectCount(query);
    return null == count ? 0 : count;
  }

  private void buildSearchWrapper(Box req, QueryWrapper<Box> query) {
    if (StrUtil.isBlank(req.getKeywords())) {
      return;
    }
    ArchiveFieldQueryDTO qryField = new ArchiveFieldQueryDTO();
    qryField.setArchTypeId(req.getArchTypeId());
    qryField.setTableId(req.getTableId());
    qryField.setEsIndex(true);

    List<ArchiveFieldListVO> fieldList = archiveFieldSvc.selectList(qryField);
    // 过滤的list为空，会报空指针异常
    List<String> fields =
        fieldList.stream().map(ArchiveFieldListVO::getDbFieldKey).collect(Collectors.toList());
    QueryUtil.buildSearchWrapper(req.getKeywords(), fields, query);
  }

  @Override
  public Integer getSequence(Map<String, Object> columnMap) {
    QueryWrapper<Box> queryWrapper = Wrappers.query();
    columnMap.forEach(queryWrapper::eq);
    int sequence = 0;
    queryWrapper.orderByDesc(ArchConst.DB_BOX_SEQUENCE);
    queryWrapper.last(" LIMIT 0, 1");
    List<Box> docList = boxMapper.selectListNoDataScope(queryWrapper);
    if (!docList.isEmpty()) {
      sequence = Optional.ofNullable(docList.get(0).getBoxSequence()).orElse(0);
    }
    return sequence;
  }

  @Override
  public Boolean checkBoxSequence(Map<String, Object> columnMap, Integer sequence) {
    QueryWrapper<Box> queryWrapper = Wrappers.query();
    columnMap.forEach(queryWrapper::eq);
    queryWrapper.orderByDesc(ArchConst.DB_BOX_SEQUENCE);
    queryWrapper.last(" LIMIT 0, 1");
    LambdaQueryWrapper<Box> lambdaQueryWrapper = queryWrapper.lambda();
    lambdaQueryWrapper.eq(Box::getBoxSequence, sequence);
    List<Box> docList = boxMapper.selectList(lambdaQueryWrapper);
    return docList.isEmpty();
  }

  @Override
  public Page<Box> getBoxByGroup(Page reqPage, Map<String, Object> columnMap, LoadBoxDTO req) {
    QueryWrapper<Box> queryWrapper = Wrappers.query();
    columnMap.forEach(queryWrapper::eq);
    queryWrapper.orderByDesc(ArchConst.DB_BOX_SEQUENCE);
    LambdaQueryWrapper<Box> boxWrapper = queryWrapper.lambda();
    boxWrapper.ne(BooleanUtil.isTrue(req.getSwitchBox()), Box::getId, req.getSwitchBoxId());
    return boxMapper.selectPage(reqPage, boxWrapper);
  }

  @Override
  public void summary(BoxSummaryDTO req) {
    Set<Long> ids = IdUtil.split(req.getDocIds());
    List<CompletableFuture<Void>> collect =
        ids.stream()
            .map(
                id ->
                    CompletableFuture.runAsync(
                        () ->
                            amsEventPublisher.publishEvent(
                                new SummaryBoxEvent(
                                    id,
                                    req.getArchTypeId(),
                                    req.getTableId(),
                                    req.getDocTableId()))))
            .collect(Collectors.toList());

    collect.stream().map(CompletableFuture::join).collect(Collectors.toList());
  }

  @Override
  public Boolean cleanTrash(BaseBO req) {
    return true;
  }

  @Override
  public Boolean recoveryData(BaseBO req) {
    Set<Long> ids = IdUtil.split(req.getBoxIds());
    this.baseMapper.recoveryData(req.getTableId(), ids);
    return null;
  }

  @Override
  public Object selectWithSummaryType(Box req, String dbFieldKey, SummaryTypeEnum type) {
    String fieldName = StringUtils.underlineToCamel(dbFieldKey);
    QueryWrapper<Box> wrapper =
        Wrappers.<Box>query()
            .select(String.format("%s(%s) as %s", type.getFunc(), dbFieldKey, fieldName))
            .eq(FieldUtil.getColumn(Box::getProjId), req.getProjId());
    Box box = boxMapper.selectOne(wrapper);
    Map<String, Object> map = BeanUtil.beanToMap(box);
    if (Objects.nonNull(map) && map.containsKey(fieldName)) {
      return map.get(fieldName);
    } else {
      return null;
    }
  }

  @Override
  public void summary(Box req) {

    ArchiveType archiveType = archiveTypeSvc.getById(req.getArchTypeId());
    // 案卷
    if (archiveType.getHaveBox()) {
      amsEventPublisher.publishEvent(
          new SummaryBoxEvent(
              req.getId(),
              req.getArchTypeId(),
              archiveType.getBoxTableId(),
              archiveType.getDocTableId()));
    }

    // 盒
    if (archiveType.getHaveProject() && Objects.nonNull(req.getProjId())) {
      amsEventPublisher.publishEvent(
          new SummaryProjectEvent(
              req.getProjId(),
              req.getArchTypeId(),
              archiveType.getProjTableId(),
              archiveType.getDocTableId(),
              archiveType.getVolTableId(),
              archiveType.getBoxTableId()));
    }
  }

  @Override
  public List<Box> selectListIgnoreFonds(BaseBO req) {
    return this.baseMapper.selectListIgnoreFonds(req.getTableId(), req.getBoxIds());
  }
}
